package com.dl698.sessionlayer.hdlc;

import com.dl698.util.ReverseByteArrayInputStream;
import com.dl698.util.ReverseByteArrayOutputStream;

/**
 * HDLC帧类
 * 实现HDLC帧的编码和解码
 * 
 * @author wind
 * @version 1.1.1
 */
public class HdlcFrame {

    private static final int FLAGBEG = 0x68;
    private static final int FLAGEND = 0x16;

    private int frameType = 0;
    private int controlField = 0;
    private byte[] informationField = null;
    private boolean segmented = false;
    private HdlcAddressPair addressPair = null;
    private int segframexh = 0;
    private int segframetype = 0;

    // 命令类型常量
    public static final int CMD_RESERVE0 = 0;
    public static final int CMD_LINK = 1;
    public static final int CMD_RESERVE2 = 2;
    public static final int CMD_USERDATA = 3;
    public static final int CMD_RESERVE4 = 4;
    public static final int CMD_RESERVE5 = 5;
    public static final int CMD_RESERVE6 = 6;
    public static final int CMD_RESERVE7 = 7;

    // 帧类型常量
    public static final int CLI_RES = 0x00;
    public static final int CLI_REQ = 0x40;
    public static final int SRV_REQ = 0x80;
    public static final int SRV_RES = 0xc0;
    public static final int ERR_INVALID_TYPE = 0xFF;

    /**
     * 默认构造函数
     */
    public HdlcFrame() {
    }

    /**
     * 设置地址对和帧类型
     * 
     * @param addressPair 地址对
     * @param frameType   帧类型
     */
    public void setAddpairFrametype(HdlcAddressPair addressPair, int frameType) {
        this.addressPair = addressPair;
        this.frameType = frameType;
    }

    /**
     * 设置所有参数
     * 
     * @param frameType        帧类型
     * @param informationField 信息字段
     * @param segmented        是否分段
     * @param controlField     控制字段
     * @param addressPair      地址对
     * @param segframexh       分段帧序号
     * @param segframetype     分段帧类型
     */
    public void setAllParam(int frameType, byte[] informationField, boolean segmented,
            int controlField, HdlcAddressPair addressPair, int segframexh,
            int segframetype) {
        this.frameType = frameType;
        this.informationField = informationField;
        this.segmented = segmented;
        this.controlField = controlField;
        this.addressPair = addressPair;
        this.segframexh = segframexh;
        this.segframetype = segframetype;
    }

    /**
     * 创建客户端请求帧
     * 
     * @param addressPair 地址对
     * @param information 信息数据
     * @param segmented   是否分段
     * @return HDLC帧
     */
    public static HdlcFrame newCliReqFrame(HdlcAddressPair addressPair, byte[] information, boolean segmented) {
        HdlcFrame hdlcFrame = new HdlcFrame();
        hdlcFrame.setAddpairFrametype(addressPair, CLI_REQ);
        hdlcFrame.informationField = information;
        hdlcFrame.segmented = segmented;
        hdlcFrame.controlField = CMD_USERDATA;
        return hdlcFrame;
    }

    /**
     * 解码HDLC帧
     * 
     * @param input 输入流
     * @return HDLC帧
     */
    public void decode(ReverseByteArrayInputStream input) {
        FcsCalc fcsCalc = new FcsCalc();
        int segframexh = 0;
        int segframetype = 0;
        int codePosition = 0;

        int frameFormatH = input.read();
        if ((frameFormatH & 0xFF) != FLAGBEG) {
            throw new RuntimeException("Illegal frameformat");
        }

        int lenframe = (input.read() & 0xff);
        lenframe += (input.read() << 8) & 0xff00;

        byte[] code = new byte[lenframe + 2];
        code[0] = (byte) (lenframe & 0xFF);
        code[1] = (byte) ((lenframe & 0xff00) >> 8);
        int controlField = input.read();
        code[2] = (byte) (controlField & 0xff);
        codePosition += 3;
        fcsCalc.update(code, codePosition);

        // 获取控制码、帧类型
        int cmdType = cmdTypeFor(controlField & 0xFF);
        int frameType = frameTypeFor(controlField & 0xFF);
        boolean segmented = (0x20 & controlField) == 0x20;

        // 地址处理
        HdlcAddress srvaddr = decodeAddress(fcsCalc, input);
        int cliaddr = input.read();
        fcsCalc.updates(cliaddr);
        int tempbyte = input.read();
        fcsCalc.updates(tempbyte);
        tempbyte = input.read();
        fcsCalc.updates(tempbyte);
        fcsCalc.validateCurrentFcsValue();

        // 信息字段处理
        byte[] informationField;
        int infoLength = lenframe - 8 - srvaddr.getByteLength();
        if (infoLength > 0) {
            if (segmented) {
                infoLength = infoLength - 2;
                int frameinfo = input.read();
                segframexh = frameinfo & 0x3FFF;
                segframetype = (frameinfo >> 14) & 0x03;
                byte[] codetemp = new byte[1000];
                codetemp[0] = (byte) (frameinfo & 0xFF);
                fcsCalc.update(codetemp, 1);
            }

            informationField = new byte[infoLength];
            for (int i = 0; i < infoLength; i++) {
                int data = input.read();
                fcsCalc.updates(data);
                informationField[i] = (byte) data;
            }

            tempbyte = input.read();
            fcsCalc.updates(tempbyte);
            tempbyte = input.read();
            fcsCalc.updates(tempbyte);
            fcsCalc.validateCurrentFcsValue();
        } else {
            informationField = new byte[0];
            tempbyte = input.read();
            fcsCalc.updates(tempbyte);
            tempbyte = input.read();
            fcsCalc.updates(tempbyte);
            fcsCalc.validateCurrentFcsValue();
        }

        tempbyte = input.read();
        HdlcAddressPair addressPair = new HdlcAddressPair(srvaddr, new byte[] { (byte) cliaddr });
        setAllParam(frameType, informationField, segmented, cmdType,
                addressPair, segframexh, segframetype);
    }

    /**
     * 解码地址
     * 
     * @param fcsCalc FCS计算器
     * @param input   输入流
     * @return HDLC地址
     */
    private static HdlcAddress decodeAddress(FcsCalc fcsCalc, ReverseByteArrayInputStream input) {
        int currentByte = input.read();
        int length = (currentByte & 0x0f) + 1;
        byte[] data = new byte[length + 1];
        fcsCalc.updates(currentByte);
        data[0] = (byte) currentByte;
        int i = 0;
        while (i < length) {
            currentByte = input.read();
            fcsCalc.updates(currentByte);
            data[++i] = (byte) currentByte;
        }
        return HdlcAddress.decode(data);
    }

    /**
     * 根据控制字节获取命令类型
     * 
     * @param controlByte 控制字节
     * @return 命令类型
     */
    private static int cmdTypeFor(int controlByte) {
        int[] cmdTypes = { CMD_RESERVE0, CMD_LINK, CMD_RESERVE2, CMD_USERDATA,
                CMD_RESERVE4, CMD_RESERVE5, CMD_RESERVE6, CMD_RESERVE7 };
        int[] masks = { 7, 7, 7, 7, 7, 7, 7, 7 };

        for (int i = 0; i < cmdTypes.length; i++) {
            if ((masks[i] & controlByte) == cmdTypes[i]) {
                return cmdTypes[i];
            }
        }
        return CMD_USERDATA;
    }

    /**
     * 根据控制字节获取帧类型
     * 
     * @param controlByte 控制字节
     * @return 帧类型
     */
    private static int frameTypeFor(int controlByte) {
        int[] frameTypes = { CLI_RES, CLI_REQ, SRV_REQ, SRV_RES, ERR_INVALID_TYPE };
        int[] masks = { 0xc0, 0xc0, 0xc0, 0xc0, 0xFF };

        for (int i = 0; i < frameTypes.length; i++) {
            if ((masks[i] & controlByte) == frameTypes[i]) {
                return frameTypes[i];
            }
        }
        return ERR_INVALID_TYPE;
    }

    /**
     * 编码HDLC帧
     * 
     * @return 编码后的字节数组
     */
    public byte[] encode() {
        byte[] data = encodeWithoutFlags();
        byte[] temp = new byte[data.length + 2];
        temp[0] = (byte) FLAGBEG;
        System.arraycopy(data, 0, temp, 1, data.length);
        temp[data.length + 1] = (byte) FLAGEND;
        return temp;
    }

    /**
     * 编码HDLC帧（不包含标志位）
     * 
     * @return 编码后的字节数组
     */
    public byte[] encodeWithoutFlags() {
        if (informationField != null) {
            int length = 8 + getSrvAddress().getByteLength();
            if (containsInformation()) {
                length += informationField.length;
            }
            if (isSegmented()) {
                length += 2;
            }

            int codePosition = 0;
            byte[] code = new byte[length];
            code[0] = (byte) (length & 0x00ff);
            code[1] = (byte) ((length & 0xff00) >> 8);
            code[2] = (byte) ((controlField & 0x03) | frameType | (isSegmented() ? 0x20 : 0x0));
            codePosition += 3;

            byte[] srvEncode = getSrvAddress().encode();
            System.arraycopy(srvEncode, 0, code, codePosition, srvEncode.length);
            codePosition += srvEncode.length;

            byte[] cliEncode = getCliAddress();
            System.arraycopy(cliEncode, 0, code, codePosition, cliEncode.length);
            codePosition += cliEncode.length;

            FcsCalc fcsCalc = new FcsCalc();
            fcsCalc.update(code, codePosition);
            byte[] hcsEncode = fcsCalc.fcsValueInBytes();
            System.arraycopy(hcsEncode, 0, code, codePosition, hcsEncode.length);
            codePosition += hcsEncode.length;
            fcsCalc.update(fcsCalc.fcsValueInBytes(), fcsCalc.fcsValueInBytes().length);

            if (isSegmented()) {
                int segframeinfo = (segframexh & 0x3FFFF) | ((segframetype & 0x03) << 14);
                code[codePosition] = (byte) (segframeinfo & 0x00ff);
                code[codePosition + 1] = (byte) ((segframeinfo & 0xff00) >> 8);
                codePosition += 2;
                byte[] segbyte = new byte[2];
                System.arraycopy(code, codePosition - 2, segbyte, 0, 2);
                fcsCalc.update(segbyte, 2);
            }

            if (containsInformation()) {
                System.arraycopy(informationField, 0, code, codePosition, informationField.length);
                codePosition += informationField.length;
                fcsCalc.update(informationField, informationField.length);
            }

            byte[] fcs = fcsCalc.fcsValueInBytes();
            System.arraycopy(fcs, 0, code, codePosition, fcs.length);
            return code;
        } else {
            throw new RuntimeException("apdu data is null");
        }
    }

    /**
     * 获取服务器地址
     * 
     * @return 服务器地址
     */
    public HdlcAddress getSrvAddress() {
        if (addressPair != null) {
            return addressPair.getSrvAddr();
        } else {
            throw new RuntimeException("addressPair is null");
        }
    }

    /**
     * 获取客户端地址
     * 
     * @return 客户端地址
     */
    public byte[] getCliAddress() {
        if (addressPair != null) {
            return addressPair.getCliAddr();
        } else {
            throw new RuntimeException("addressPair is null");
        }
    }

    /**
     * 获取地址对
     * 
     * @return 地址对
     */
    public HdlcAddressPair getAddressPair() {
        if (addressPair != null) {
            return addressPair;
        } else {
            throw new RuntimeException("addressPair is null");
        }
    }

    /**
     * 获取帧类型
     * 
     * @return 帧类型
     */
    public int getFrameType() {
        return frameType;
    }

    /**
     * 获取信息字段
     * 
     * @return 信息字段
     */
    public byte[] getInformationField() {
        if (informationField != null) {
            return informationField;
        } else {
            throw new RuntimeException("apdu data is null");
        }
    }

    /**
     * 是否分段
     * 
     * @return true如果分段
     */
    public boolean isSegmented() {
        return segmented;
    }

    /**
     * 是否包含信息
     * 
     * @return true如果包含信息
     */
    public boolean containsInformation() {
        return informationField != null;
    }

    /**
     * 设置分段帧类型
     * 
     * @param segframetype 分段帧类型
     */
    public void setSegFrameType(int segframetype) {
        this.segframetype = segframetype;
    }

    /**
     * 设置分段帧序号
     * 
     * @param segframexh 分段帧序号
     */
    public void setSegFrameXh(int segframexh) {
        this.segframexh = segframexh;
    }

    /**
     * 获取分段帧类型
     * 
     * @return 分段帧类型
     */
    public int getSegFrameType() {
        return segframetype;
    }

    /**
     * 获取分段帧序号
     * 
     * @return 分段帧序号
     */
    public int getSegFrameXh() {
        return segframexh;
    }
}